Use exact per-file environments for Python files (PEP 723 PR 19) - #26129
Conversation
Resolve debugger programs and Pylance Python-file configuration against the exact file resource without publishing false workspace interpreter changes. Preserve workspace fallback and activate differing program environments. Part of microsoft/vscode-python-environments#1602. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2ea8f5e to
e413c74
Compare
|
🔒 Automated review in progress — Bill Schnurr (@bschnurr) is auto-reviewing this PR. |
|
Verification: The relevant tests could not be fully run in the isolated environment; this review is not fully verified. Result: Summary: Verification could not execute because no trusted sandbox image is configured for `microsoft/vscode-python`, and local execution was not authorized. The PR adds eight focused unit tests covering exact interpreter lookup, debugger selection, activation, and Pylance configuration. None could be run, so no pass/fail confidence is available. Two newly introduced debugger resolution paths appear uncovered. Test runs: 1 not run
|
Bill Schnurr (bschnurr)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
|
Verification: The relevant tests could not be fully run in the isolated environment; this review is not fully verified. Result: Summary: Verification was blocked because no trusted sandbox image is configured for `microsoft/vscode-python`, and local execution was not authorized. No tests meaningfully ran. The PR adds eight focused unit tests, but `${file}` debugger program resolution lacks explicit coverage. Test runs: 1 not run
|
|
Verification: The relevant tests could not be fully run in the isolated environment; this review is not fully verified. Result: Summary: Verification could not run because no trusted sandbox image is configured for `microsoft/vscode-python`, and local execution was not authorized. The PR adds nine focused unit tests covering exact interpreter lookup, debugger selection/fallback, named workspaces, and activation. One documented path, `${file}` debugger program resolution, has no test coverage I could find. Test runs: 1 not run
|
Clarify why the workspace/configuration handler resolves the interpreter for the exact .py file (exactResource) rather than its folder: it honors a per-file environment such as a PEP 723 inline-script env for the language client this extension hosts, and only diverges from folder resolution when the Python Environments extension is in use. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The debug resolver now resolves \/\ to a program URI and looks up the interpreter for that file. To avoid changing behavior for users who are not using the environments extension (e.g. multi-root debugging of a file that lives in a different folder than the launch config), only take the program-scoped path when useEnvExtension() is true; otherwise fall back to the historical workspace-folder resolution. Environments-extension users still get per-file (inline-script) resolution. Stub useEnvExtension() in the existing program-scoped resolver tests and add a test covering the gated fallback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2d58111 to
67eef40
Compare
|
Verification: The relevant tests could not be fully run in the isolated environment; this review is not fully verified. Result: Summary: No tests could run because the verification container lacked a trusted `microsoft/vscode-python` sandbox image, and local execution was not authorized. The PR adds nine focused unit-test cases covering exact interpreter lookup, debugger resolution, activation, and Pylance configuration. `${file}`-based debugger interpreter selection remains without direct coverage. Test runs: 1 not run
|
7c12038
into
microsoft:main
Rich Chiodo (rchiodo)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
> Part of #1602 (PEP 723 inline script env support). Design doc: #1601. ### Roadmap context This is **PR 10 of 16** in the PEP 723 inline-script roadmap. PRs 7-9 persist, discover, validate, and route per-script environments; this PR gives each configured script an exact project identity that survives restart and can be consumed by per-file integrations. | Phase 3 / integration | PR | Status | |---|---|---| | | PR 7: per-script persistence | merged (#1697) | | | PR 8: activation-time discovery | merged (#1722) | | | PR 9: automatic per-script routing | merged (#1729) | | | **PR 10: exact script project registration** | **this PR** | | | PRs 11-12: CodeLens and bulk setup UX | follow-up | | | PR 17: Pylance per-file Python path | [microsoft/pyrx#9265](microsoft/pyrx#9265) | | | PR 19: Python extension per-file lookup | [microsoft/vscode-python#26129](microsoft/vscode-python#26129) | ### Why this PR PR 9 can route a saved script to a validated inline environment, but the project manager still identifies the script through its containing workspace project. That prevents the per-file identity from surviving restart consistently and leaves downstream configuration and environment-change consumers without an exact script scope. The registration also needs an ownership boundary: clearing inline environments must remove entries created by the extension without deleting user-authored project settings. ### What this PR does - Registers an exact `pythonProjects` entry before binding an inline environment. - Stores the normal environment and package manager as the script's fallback rather than replacing them with the inline manager. - Marks extension-managed entries as either: - `created`: remove the entry during inline cleanup; - `adopted`: remove only the marker and preserve the user's entry. - Ignores the managed fallback entry while a validated inline association is routeable. - Uses that entry normally when the feature is disabled or the association becomes stale. - Rolls back a newly prepared registration if inline association binding fails. - Supports single and batch script selection. - Converts a managed entry into an ordinary user-owned entry when the user explicitly selects a non-inline manager. - Coordinates explicit managed selections with the dedicated clear-cache operation. - Resolves and updates same-named scripts correctly in multi-root workspaces. ### Registration and cleanup semantics | Condition | Behavior | |---|---| | No exact project entry exists | Create a marked entry containing the ordinary fallback managers | | An exact user entry exists | Temporarily mark it without changing its manager choices | | Inline binding fails | Roll back the marker/created entry and any newly added in-memory project | | Validated association exists | Route through the inline manager | | Association is stale or unavailable | Use the stored fallback manager | | User selects a non-inline manager | Update the owning setting and remove the managed marker | | Clear Script Environment Cache | Remove created entries; restore adopted entries | | Two roots contain the same relative script path | Match using the `workspace` discriminator | ### Performance and safety - No workspace scan is introduced. - Configuration writes occur only during explicit persisted selection, rollback, manager changes, or dedicated cache cleanup. - Serialization is limited to managed inline-script project mutations; unrelated environment routing and refresh operations are not queued. - User-owned project settings are never deleted by inline cleanup. ### User impact The feature remains behind `python-envs.inlineScripts.enabled`. Existing users and projects without a managed inline-script entry retain their current manager-selection behavior. After setup, a script has a stable exact project scope across reloads; when inline routing is unavailable, its previous project/workspace environment remains the fallback. ### Tests - `npm run compile-tests` - `npm run compile` - `npm run lint` - Focused command, environment-manager, and settings tests: **92 passing** - The full unit suite was also run. Six unchanged timing-sensitive inline-manager tests failed only under full-suite load and passed together in an isolated rerun. ### Scope and follow-up This PR does not add the setup CodeLens, bulk setup command, TTL eviction, or public feature enablement. Those remain in PRs 11, 12, and 14. Per-file language-service and debugger integration are handled by the companion cross-repository PRs above. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
) ## Problem A PEP 723 inline-script environment is built correctly by this extension on its own, but it only reaches the language server once two companion changes ship: - **Python extension** resolves interpreters per file (`exactResource`, microsoft/vscode-python#26129) - **Pylance** handles the file-scoped `python/didChangeFilePythonPath` notification (PR #9302) Until both are present, setup reports success and the user is left without the full inline script experience, with nothing explaining why. ## Change Warn once, **after a successful setup** rather than before one. That is the moment the gap becomes visible, and the creation progress notification is already gone — prompting up front would be noise stacked on top of it. Both entry points prompt at the handler level (`setupInlineScriptEnvironmentHandler` and `setUpInlineScriptEnvironmentsInWorkspace`), so a bulk run over N scripts shows **one** notification rather than N. The prompt offers **Update Extension** and **Don't Show Again**; the latter persists in global state. Dismissing without choosing an action suppresses it for the session only. ## Version handling | | stable | pre-release | |---|---|---| | Python | `2026.4.0` | `2026.7.2026082601` | | Pylance | `2026.3.1` | `2026.3.101` | Values are the newest version on each channel that still **lacks** the change; anything newer passes. Notes on why this is more involved than a single `>=`: - **Per channel, because the two lines interleave numerically.** Pre-release `2026.5.x`/`2026.7.x` sort *above* stable `2026.4.0`, so one threshold cannot express both — a future stable such as `2026.6.0` would be wrongly rejected. - **Channel is inferred from the patch component.** VS Code does not populate `__metadata.preRelease` for installed extensions (verified empty on every installed extension locally, including a known pre-release build). Python pre-releases carry a build-date patch, Pylance pre-releases use a 100+ counter. Both rules were checked against every published version above the relevant floors. - **PEP 440 ordering**, since these extensions are not semver. - **Dev builds are skipped, not compared.** `2026.7.0-dev` is valid PEP 440 and normalizes to `2026.7.0.dev0`, which sorts below every real build of the same minor — comparing it would flag anyone running a local build of either extension. - **A missing extension is not outdated.** Pylance is optional. - **Pylance is skipped unless `python.languageServer` selects it**, so Jedi and None users are not asked to update an extension they do not use. Each outdated combination has its own complete l10n string rather than a joined extension list, since conjunctions and word order are locale-specific. ## Follow-up The thresholds are a snapshot of the newest version lacking the change. **If either extension ships another release before the change lands, the corresponding constant must be bumped**, or the warning silently stops firing for users on that release. Once the real shipping versions are known, consider inverting to "first version *with* the change" (`<` instead of `<=`): that framing fails toward an over-eager prompt, which is visible and reported, rather than toward silence. ## Testing 36 new unit tests in `src/test/features/inlineScript/extensionVersionCheck.unit.test.ts`, covering both channels for both extensions, the dev-build case, the language-server gate, and all dismissal paths. Full unit suite: **2047 passing, 0 failing** (clean `out/` rebuild). `tsc --noEmit`, `eslint src`, and `prettier --check` all clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Roadmap context
This is cross-repository PR 19 in the PEP 723 roadmap. It closes the Python extension's per-file lookup gaps for debugging and Pylance configuration.
Why this PR
IInterpreterService.getActiveInterpreter(resource)normally shares in-flight, timeout, and last-known state by workspace folder. A file URI can therefore receive the workspace interpreter when a workspace lookup is already running or when exact environment resolution exceeds the timeout.That breaks two per-file consumers:
workspace/configurationrequest can receive a cached workspace interpreter.The exact lookup must also avoid publishing a file interpreter as a workspace-wide interpreter change.
What this PR does
exactResourceoption toIInterpreterService.${file};${workspaceFolder};${workspaceFolder:name}.pythonPathand command-valuedpython..py-scoped Pylance configuration requests; workspace-level requests retain the existing cached fast path.Lookup semantics
pythonconfig for a.pyURIPerformance and safety
.pyconfiguration scopes.User impact
Users without a per-file environment retain the same interpreter and debugger behavior. When a Python file has a distinct environment, Pylance configuration and debugger launch consistently use that file's interpreter rather than a workspace-cached value.
Tests
TelemetryReporterimport errors in untouched files.Scope and follow-up
This PR does not implement Pylance's open-file rerouting notification. Live movement and reanalysis after a per-file environment change remain in PR 18.